# ============================================================================
# ===( Class Client  )========================================================
# ============================================================================

from json import loads


class _client :

    # ------------------------------------------------------------------------

    def __init__(self, microWebSrv, socket, addr) :
        socket.settimeout(2)
        self._microWebSrv   = microWebSrv
        self._socket        = socket
        self._addr          = addr
        self._method        = None
        self._path          = None
        self._httpVer       = None
        self._resPath       = "/"
        self._queryString   = ""
        self._queryParams   = { }
        self._headers       = { }
        self._contentType   = None
        self._contentLength = 0
        
        if hasattr(socket, 'readline'):   # MicroPython
            self._socketfile = self._socket
        else:   # CPython
            self._socketfile = self._socket.makefile('rwb')
                    
        self._processRequest()

    # ------------------------------------------------------------------------

    def _processRequest(self) :
        from .microwebsrv_resp import _response
        from .microWebSrv import MicroWebSrv
        try :
            response = _response(self)
            if self._parseFirstLine(response) :
                if self._parseHeader(response) :
                    upg = self._getConnUpgrade()
                    if not upg :
                        routeHandler, routeArgs = self._microWebSrv.GetRouteHandler(self._resPath, self._method)
                        if routeHandler :
                            try :
                                if routeArgs is not None:
                                    routeHandler(self, response, routeArgs)
                                else :
                                    routeHandler(self, response)
                            except Exception as ex :
                                import sys
                                sys.print_exception(ex)
                                print('MicroWebSrv handler exception:\r\n  - In route %s %s\r\n  - %s' % (self._method, self._resPath, ex))
                                raise ex
                        elif self._method.upper() == "GET" :
                            filepath = self._microWebSrv._physPathFromURLPath(self._resPath)
                            if filepath :
                                if MicroWebSrv._isPyHTMLFile(filepath) :
                                    response.WriteResponsePyHTMLFile(filepath)
                                else :
                                    contentType = self._microWebSrv.GetMimeTypeFromFilename(filepath)
                                    if contentType :
                                        if self._microWebSrv.LetCacheStaticContentLevel > 0 :
                                            if self._microWebSrv.LetCacheStaticContentLevel > 1 and \
                                                'if-modified-since' in self._headers :
                                                response.WriteResponseNotModified()
                                            else:
                                                headers = { 'Last-Modified' : 'Fri, 1 Jan 2018 23:42:00 GMT', \
                                                            'Cache-Control' : 'max-age=315360000' }
                                                response.WriteResponseFile(filepath, contentType, headers)
                                        else :
                                            response.WriteResponseFile(filepath, contentType)
                                    else :
                                        response.WriteResponseForbidden()
                            else :
                                response.WriteResponseNotFound()
                        else :
                            response.WriteResponseMethodNotAllowed()
                    elif upg == 'websocket' and 'MicroWebSocket' in globals() \
                            and self._microWebSrv.AcceptWebSocketCallback :
                            MicroWebSocket( socket         = self._socket,
                                            httpClient     = self,
                                            httpResponse   = response,
                                            maxRecvLen     = self._microWebSrv.MaxWebSocketRecvLen,
                                            threaded       = self._microWebSrv.WebSocketThreaded,
                                            acceptCallback = self._microWebSrv.AcceptWebSocketCallback )
                            return
                    else :
                        response.WriteResponseNotImplemented()
                else :
                    response.WriteResponseBadRequest()
        except :
            response.WriteResponseInternalServerError()
        try :
            if self._socketfile is not self._socket:
                self._socketfile.close()
            self._socket.close()
        except :
            pass

    # ------------------------------------------------------------------------

    def _parseFirstLine(self, response) :
        from .microWebSrv import MicroWebSrv
        try :
            elements = self._socketfile.readline().decode().strip().split()
            if len(elements) == 3 :
                self._method  = elements[0].upper()
                self._path    = elements[1]
                self._httpVer = elements[2].upper()
                elements      = self._path.split('?', 1)
                if len(elements) > 0 :
                    self._resPath = MicroWebSrv._unquote_plus(elements[0])
                    if len(elements) > 1 :
                        self._queryString = elements[1]
                        elements = self._queryString.split('&')
                        for s in elements :
                            param = s.split('=', 1)
                            if len(param) > 0 :
                                value = MicroWebSrv._unquote(param[1]) if len(param) > 1 else ''
                                self._queryParams[MicroWebSrv._unquote(param[0])] = value
                return True
        except :
            pass
        return False

    # ------------------------------------------------------------------------

    def _parseHeader(self, response) :
        while True :
            elements = self._socketfile.readline().decode().strip().split(':', 1)
            if len(elements) == 2 :
                self._headers[elements[0].strip().lower()] = elements[1].strip()
            elif len(elements) == 1 and len(elements[0]) == 0 :
                if self._method == 'POST' or self._method == 'PUT' :
                    self._contentType   = self._headers.get("content-type", None)
                    self._contentLength = int(self._headers.get("content-length", 0))
                return True
            else :
                return False

    # ------------------------------------------------------------------------

    def _getConnUpgrade(self) :
        if 'upgrade' in self._headers.get('connection', '').lower() :
            return self._headers.get('upgrade', '').lower()
        return None

    # ------------------------------------------------------------------------

    def GetServer(self) :
        return self._microWebSrv

    # ------------------------------------------------------------------------

    def GetAddr(self) :
        return self._addr

    # ------------------------------------------------------------------------

    def GetIPAddr(self) :
        return self._addr[0]

    # ------------------------------------------------------------------------

    def GetPort(self) :
        return self._addr[1]

    # ------------------------------------------------------------------------

    def GetRequestMethod(self) :
        return self._method

    # ------------------------------------------------------------------------

    def GetRequestTotalPath(self) :
        return self._path

    # ------------------------------------------------------------------------

    def GetRequestPath(self) :
        return self._resPath

    # ------------------------------------------------------------------------

    def GetRequestQueryString(self) :
        return self._queryString

    # ------------------------------------------------------------------------

    def GetRequestQueryParams(self) :
        return self._queryParams

    # ------------------------------------------------------------------------

    def GetRequestHeaders(self) :
        return self._headers

    # ------------------------------------------------------------------------

    def GetRequestContentType(self) :
        return self._contentType

    # ------------------------------------------------------------------------

    def GetRequestContentLength(self) :
        return self._contentLength

    # ------------------------------------------------------------------------

    def ReadRequestContent(self, size=None) :
        if size is None :
            size = self._contentLength
        if size > 0 :
            try :
                return self._socketfile.read(size)
            except :
                pass
        return b''

    # ------------------------------------------------------------------------

    def ReadRequestPostedFormData(self) :
        from .microWebSrv import MicroWebSrv
        res  = { }
        data = self.ReadRequestContent()
        if data :
            elements = data.decode().split('&')
            for s in elements :
                param = s.split('=', 1)
                if len(param) > 0 :
                    value = MicroWebSrv._unquote_plus(param[1]) if len(param) > 1 else ''
                    res[MicroWebSrv._unquote_plus(param[0])] = value
        return res

    # ------------------------------------------------------------------------

    def ReadRequestContentAsJSON(self) :
        data = self.ReadRequestContent()
        if data :
            try :
                return loads(data.decode())
            except :
                pass
        return None
    
